// Copyright 2012 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/ast/scopes.h"

#include <set>

#include "src/accessors.h"
#include "src/ast/ast.h"
#include "src/base/optional.h"
#include "src/bootstrapper.h"
#include "src/counters.h"
#include "src/message-template.h"
#include "src/objects-inl.h"
#include "src/objects/module-inl.h"
#include "src/objects/scope-info.h"
#include "src/parsing/parse-info.h"
#include "src/parsing/parser.h"
#include "src/parsing/preparse-data.h"
#include "src/zone/zone-list-inl.h"

namespace v8 {
namespace internal {

    // ----------------------------------------------------------------------------
    // Implementation of LocalsMap
    //
    // Note: We are storing the handle locations as key values in the hash map.
    //       When inserting a new variable via Declare(), we rely on the fact that
    //       the handle location remains alive for the duration of that variable
    //       use. Because a Variable holding a handle with the same location exists
    //       this is ensured.

    VariableMap::VariableMap(Zone* zone)
        : ZoneHashMap(8, ZoneAllocationPolicy(zone))
    {
    }

    Variable* VariableMap::Declare(Zone* zone, Scope* scope,
        const AstRawString* name, VariableMode mode,
        VariableKind kind,
        InitializationFlag initialization_flag,
        MaybeAssignedFlag maybe_assigned_flag,
        bool* was_added)
    {
        // AstRawStrings are unambiguous, i.e., the same string is always represented
        // by the same AstRawString*.
        // FIXME(marja): fix the type of Lookup.
        Entry* p = ZoneHashMap::LookupOrInsert(const_cast<AstRawString*>(name), name->Hash(),
            ZoneAllocationPolicy(zone));
        *was_added = p->value == nullptr;
        if (*was_added) {
            // The variable has not been declared yet -> insert it.
            DCHECK_EQ(name, p->key);
            Variable* variable = new (zone) Variable(
                scope, name, mode, kind, initialization_flag, maybe_assigned_flag);
            p->value = variable;
        }
        return reinterpret_cast<Variable*>(p->value);
    }

    void VariableMap::Remove(Variable* var)
    {
        const AstRawString* name = var->raw_name();
        ZoneHashMap::Remove(const_cast<AstRawString*>(name), name->Hash());
    }

    void VariableMap::Add(Zone* zone, Variable* var)
    {
        const AstRawString* name = var->raw_name();
        Entry* p = ZoneHashMap::LookupOrInsert(const_cast<AstRawString*>(name), name->Hash(),
            ZoneAllocationPolicy(zone));
        DCHECK_NULL(p->value);
        DCHECK_EQ(name, p->key);
        p->value = var;
    }

    Variable* VariableMap::Lookup(const AstRawString* name)
    {
        Entry* p = ZoneHashMap::Lookup(const_cast<AstRawString*>(name), name->Hash());
        if (p != nullptr) {
            DCHECK(reinterpret_cast<const AstRawString*>(p->key) == name);
            DCHECK_NOT_NULL(p->value);
            return reinterpret_cast<Variable*>(p->value);
        }
        return nullptr;
    }

    // ----------------------------------------------------------------------------
    // Implementation of Scope

    Scope::Scope(Zone* zone)
        : zone_(zone)
        , outer_scope_(nullptr)
        , variables_(zone)
        , scope_type_(SCRIPT_SCOPE)
    {
        SetDefaults();
    }

    Scope::Scope(Zone* zone, Scope* outer_scope, ScopeType scope_type)
        : zone_(zone)
        , outer_scope_(outer_scope)
        , variables_(zone)
        , scope_type_(scope_type)
    {
        DCHECK_NE(SCRIPT_SCOPE, scope_type);
        SetDefaults();
        set_language_mode(outer_scope->language_mode());
        outer_scope_->AddInnerScope(this);
    }

    DeclarationScope::DeclarationScope(Zone* zone,
        AstValueFactory* ast_value_factory)
        : Scope(zone)
        , function_kind_(kNormalFunction)
        , params_(4, zone)
    {
        DCHECK_EQ(scope_type_, SCRIPT_SCOPE);
        SetDefaults();
        receiver_ = DeclareDynamicGlobal(ast_value_factory->this_string(),
            THIS_VARIABLE, this);
    }

    DeclarationScope::DeclarationScope(Zone* zone, Scope* outer_scope,
        ScopeType scope_type,
        FunctionKind function_kind)
        : Scope(zone, outer_scope, scope_type)
        , function_kind_(function_kind)
        , params_(4, zone)
    {
        DCHECK_NE(scope_type, SCRIPT_SCOPE);
        SetDefaults();
    }

    ModuleScope::ModuleScope(DeclarationScope* script_scope,
        AstValueFactory* avfactory)
        : DeclarationScope(avfactory->zone(), script_scope, MODULE_SCOPE, kModule)
        , module_descriptor_(new (avfactory->zone())
                  ModuleDescriptor(avfactory->zone()))
    {
        set_language_mode(LanguageMode::kStrict);
        DeclareThis(avfactory);
    }

    ModuleScope::ModuleScope(Isolate* isolate, Handle<ScopeInfo> scope_info,
        AstValueFactory* avfactory)
        : DeclarationScope(avfactory->zone(), MODULE_SCOPE, scope_info)
        , module_descriptor_(nullptr)
    {
        set_language_mode(LanguageMode::kStrict);
    }

    ClassScope::ClassScope(Zone* zone, Scope* outer_scope)
        : Scope(zone, outer_scope, CLASS_SCOPE)
    {
        set_language_mode(LanguageMode::kStrict);
    }

    ClassScope::ClassScope(Zone* zone, Handle<ScopeInfo> scope_info)
        : Scope(zone, CLASS_SCOPE, scope_info)
    {
        set_language_mode(LanguageMode::kStrict);
    }

    Scope::Scope(Zone* zone, ScopeType scope_type, Handle<ScopeInfo> scope_info)
        : zone_(zone)
        , outer_scope_(nullptr)
        , variables_(zone)
        , scope_info_(scope_info)
        , scope_type_(scope_type)
    {
        DCHECK(!scope_info.is_null());
        SetDefaults();
#ifdef DEBUG
        already_resolved_ = true;
#endif
        if (scope_info->CallsSloppyEval())
            scope_calls_eval_ = true;
        set_language_mode(scope_info->language_mode());
        num_heap_slots_ = scope_info->ContextLength();
        DCHECK_LE(Context::MIN_CONTEXT_SLOTS, num_heap_slots_);
        // We don't really need to use the preparsed scope data; this is just to
        // shorten the recursion in SetMustUsePreparseData.
        must_use_preparsed_scope_data_ = true;
    }

    DeclarationScope::DeclarationScope(Zone* zone, ScopeType scope_type,
        Handle<ScopeInfo> scope_info)
        : Scope(zone, scope_type, scope_info)
        , function_kind_(scope_info->function_kind())
        , params_(0, zone)
    {
        DCHECK_NE(scope_type, SCRIPT_SCOPE);
        SetDefaults();
    }

    Scope::Scope(Zone* zone, const AstRawString* catch_variable_name,
        MaybeAssignedFlag maybe_assigned, Handle<ScopeInfo> scope_info)
        : zone_(zone)
        , outer_scope_(nullptr)
        , variables_(zone)
        , scope_info_(scope_info)
        , scope_type_(CATCH_SCOPE)
    {
        SetDefaults();
#ifdef DEBUG
        already_resolved_ = true;
#endif
        // Cache the catch variable, even though it's also available via the
        // scope_info, as the parser expects that a catch scope always has the catch
        // variable as first and only variable.
        bool was_added;
        Variable* variable = Declare(zone, catch_variable_name, VariableMode::kVar, NORMAL_VARIABLE,
            kCreatedInitialized, maybe_assigned, &was_added);
        DCHECK(was_added);
        AllocateHeapSlot(variable);
    }

    void DeclarationScope::SetDefaults()
    {
        is_declaration_scope_ = true;
        has_simple_parameters_ = true;
        is_asm_module_ = false;
        force_eager_compilation_ = false;
        has_arguments_parameter_ = false;
        scope_uses_super_property_ = false;
        has_checked_syntax_ = false;
        has_this_reference_ = false;
        has_this_declaration_ = (is_function_scope() && !is_arrow_scope()) || is_module_scope();
        has_rest_ = false;
        receiver_ = nullptr;
        new_target_ = nullptr;
        function_ = nullptr;
        arguments_ = nullptr;
        rare_data_ = nullptr;
        should_eager_compile_ = false;
        was_lazily_parsed_ = false;
        is_skipped_function_ = false;
        preparse_data_builder_ = nullptr;
#ifdef DEBUG
        DeclarationScope* outer_declaration_scope = outer_scope_ ? outer_scope_->GetDeclarationScope() : nullptr;
        is_being_lazily_parsed_ = outer_declaration_scope ? outer_declaration_scope->is_being_lazily_parsed_
                                                          : false;
#endif
    }

    void Scope::SetDefaults()
    {
#ifdef DEBUG
        scope_name_ = nullptr;
        already_resolved_ = false;
        needs_migration_ = false;
#endif
        inner_scope_ = nullptr;
        sibling_ = nullptr;
        unresolved_list_.Clear();

        start_position_ = kNoSourcePosition;
        end_position_ = kNoSourcePosition;

        num_stack_slots_ = 0;
        num_heap_slots_ = Context::MIN_CONTEXT_SLOTS;

        set_language_mode(LanguageMode::kSloppy);

        scope_calls_eval_ = false;
        scope_nonlinear_ = false;
        is_hidden_ = false;
        is_debug_evaluate_scope_ = false;

        inner_scope_calls_eval_ = false;
        force_context_allocation_ = false;
        force_context_allocation_for_parameters_ = false;

        is_declaration_scope_ = false;

        must_use_preparsed_scope_data_ = false;
    }

    bool Scope::HasSimpleParameters()
    {
        DeclarationScope* scope = GetClosureScope();
        return !scope->is_function_scope() || scope->has_simple_parameters();
    }

    void DeclarationScope::set_should_eager_compile()
    {
        should_eager_compile_ = !was_lazily_parsed_;
    }

    void DeclarationScope::set_is_asm_module() { is_asm_module_ = true; }

    bool Scope::IsAsmModule() const
    {
        return is_function_scope() && AsDeclarationScope()->is_asm_module();
    }

    bool Scope::ContainsAsmModule() const
    {
        if (IsAsmModule())
            return true;

        // Check inner scopes recursively
        for (Scope* scope = inner_scope_; scope != nullptr; scope = scope->sibling_) {
            // Don't check inner functions which won't be eagerly compiled.
            if (!scope->is_function_scope() || scope->AsDeclarationScope()->ShouldEagerCompile()) {
                if (scope->ContainsAsmModule())
                    return true;
            }
        }

        return false;
    }

    Scope* Scope::DeserializeScopeChain(Isolate* isolate, Zone* zone,
        ScopeInfo scope_info,
        DeclarationScope* script_scope,
        AstValueFactory* ast_value_factory,
        DeserializationMode deserialization_mode)
    {
        // Reconstruct the outer scope chain from a closure's context chain.
        Scope* current_scope = nullptr;
        Scope* innermost_scope = nullptr;
        Scope* outer_scope = nullptr;
        while (!scope_info.is_null()) {
            if (scope_info->scope_type() == WITH_SCOPE) {
                if (scope_info->IsDebugEvaluateScope()) {
                    outer_scope = new (zone)
                        DeclarationScope(zone, FUNCTION_SCOPE, handle(scope_info, isolate));
                    outer_scope->set_is_debug_evaluate_scope();
                } else {
                    // For scope analysis, debug-evaluate is equivalent to a with scope.
                    outer_scope = new (zone) Scope(zone, WITH_SCOPE, handle(scope_info, isolate));
                }

            } else if (scope_info->scope_type() == SCRIPT_SCOPE) {
                // If we reach a script scope, it's the outermost scope. Install the
                // scope info of this script context onto the existing script scope to
                // avoid nesting script scopes.
                if (deserialization_mode == DeserializationMode::kIncludingVariables) {
                    script_scope->SetScriptScopeInfo(handle(scope_info, isolate));
                }
                DCHECK(!scope_info->HasOuterScopeInfo());
                break;
            } else if (scope_info->scope_type() == FUNCTION_SCOPE) {
                outer_scope = new (zone)
                    DeclarationScope(zone, FUNCTION_SCOPE, handle(scope_info, isolate));
                if (scope_info->IsAsmModule()) {
                    outer_scope->AsDeclarationScope()->set_is_asm_module();
                }
            } else if (scope_info->scope_type() == EVAL_SCOPE) {
                outer_scope = new (zone)
                    DeclarationScope(zone, EVAL_SCOPE, handle(scope_info, isolate));
            } else if (scope_info->scope_type() == CLASS_SCOPE) {
                outer_scope = new (zone) ClassScope(zone, handle(scope_info, isolate));
            } else if (scope_info->scope_type() == BLOCK_SCOPE) {
                if (scope_info->is_declaration_scope()) {
                    outer_scope = new (zone)
                        DeclarationScope(zone, BLOCK_SCOPE, handle(scope_info, isolate));
                } else {
                    outer_scope = new (zone) Scope(zone, BLOCK_SCOPE, handle(scope_info, isolate));
                }
            } else if (scope_info->scope_type() == MODULE_SCOPE) {
                outer_scope = new (zone)
                    ModuleScope(isolate, handle(scope_info, isolate), ast_value_factory);
            } else {
                DCHECK_EQ(scope_info->scope_type(), CATCH_SCOPE);
                DCHECK_EQ(scope_info->ContextLocalCount(), 1);
                DCHECK_EQ(scope_info->ContextLocalMode(0), VariableMode::kVar);
                DCHECK_EQ(scope_info->ContextLocalInitFlag(0), kCreatedInitialized);
                String name = scope_info->ContextLocalName(0);
                MaybeAssignedFlag maybe_assigned = scope_info->ContextLocalMaybeAssignedFlag(0);
                outer_scope = new (zone)
                    Scope(zone, ast_value_factory->GetString(handle(name, isolate)),
                        maybe_assigned, handle(scope_info, isolate));
            }
            if (deserialization_mode == DeserializationMode::kScopesOnly) {
                outer_scope->scope_info_ = Handle<ScopeInfo>::null();
            }
            if (current_scope != nullptr) {
                outer_scope->AddInnerScope(current_scope);
            }
            current_scope = outer_scope;
            if (innermost_scope == nullptr)
                innermost_scope = current_scope;
            scope_info = scope_info->HasOuterScopeInfo() ? scope_info->OuterScopeInfo()
                                                         : ScopeInfo();
        }

        if (deserialization_mode == DeserializationMode::kIncludingVariables && script_scope->scope_info_.is_null()) {
            Handle<ScriptContextTable> table(
                isolate->native_context()->script_context_table(), isolate);
            Handle<Context> first = ScriptContextTable::GetContext(isolate, table, 0);
            Handle<ScopeInfo> scope_info(first->scope_info(), isolate);
            script_scope->SetScriptScopeInfo(scope_info);
        }

        if (innermost_scope == nullptr)
            return script_scope;
        script_scope->AddInnerScope(current_scope);
        return innermost_scope;
    }

    DeclarationScope* Scope::AsDeclarationScope()
    {
        DCHECK(is_declaration_scope());
        return static_cast<DeclarationScope*>(this);
    }

    const DeclarationScope* Scope::AsDeclarationScope() const
    {
        DCHECK(is_declaration_scope());
        return static_cast<const DeclarationScope*>(this);
    }

    ModuleScope* Scope::AsModuleScope()
    {
        DCHECK(is_module_scope());
        return static_cast<ModuleScope*>(this);
    }

    const ModuleScope* Scope::AsModuleScope() const
    {
        DCHECK(is_module_scope());
        return static_cast<const ModuleScope*>(this);
    }

    ClassScope* Scope::AsClassScope()
    {
        DCHECK(is_class_scope());
        return static_cast<ClassScope*>(this);
    }

    const ClassScope* Scope::AsClassScope() const
    {
        DCHECK(is_class_scope());
        return static_cast<const ClassScope*>(this);
    }

    void DeclarationScope::DeclareSloppyBlockFunction(
        SloppyBlockFunctionStatement* sloppy_block_function)
    {
        sloppy_block_functions_.Add(sloppy_block_function);
    }

    void DeclarationScope::HoistSloppyBlockFunctions(AstNodeFactory* factory)
    {
        DCHECK(is_sloppy(language_mode()));
        DCHECK(is_function_scope() || is_eval_scope() || is_script_scope() || (is_block_scope() && outer_scope()->is_function_scope()));
        DCHECK(HasSimpleParameters() || is_block_scope() || is_being_lazily_parsed_);
        DCHECK_EQ(factory == nullptr, is_being_lazily_parsed_);

        if (sloppy_block_functions_.is_empty())
            return;

        // In case of complex parameters the current scope is the body scope and the
        // parameters are stored in the outer scope.
        Scope* parameter_scope = HasSimpleParameters() ? this : outer_scope_;
        DCHECK(parameter_scope->is_function_scope() || is_eval_scope() || is_script_scope());

        DeclarationScope* decl_scope = this;
        while (decl_scope->is_eval_scope()) {
            decl_scope = decl_scope->outer_scope()->GetDeclarationScope();
        }
        Scope* outer_scope = decl_scope->outer_scope();

        // For each variable which is used as a function declaration in a sloppy
        // block,
        for (SloppyBlockFunctionStatement* sloppy_block_function :
            sloppy_block_functions_) {
            const AstRawString* name = sloppy_block_function->name();

            // If the variable wouldn't conflict with a lexical declaration
            // or parameter,

            // Check if there's a conflict with a parameter.
            Variable* maybe_parameter = parameter_scope->LookupLocal(name);
            if (maybe_parameter != nullptr && maybe_parameter->is_parameter()) {
                continue;
            }

            // Check if there's a conflict with a lexical declaration
            Scope* query_scope = sloppy_block_function->scope()->outer_scope();
            Variable* var = nullptr;
            bool should_hoist = true;

            // It is not sufficient to just do a Lookup on query_scope: for
            // example, that does not prevent hoisting of the function in
            // `{ let e; try {} catch (e) { function e(){} } }`
            do {
                var = query_scope->LookupInScopeOrScopeInfo(name);
                if (var != nullptr && IsLexicalVariableMode(var->mode())) {
                    should_hoist = false;
                    break;
                }
                query_scope = query_scope->outer_scope();
            } while (query_scope != outer_scope);

            if (!should_hoist)
                continue;

            if (factory) {
                DCHECK(!is_being_lazily_parsed_);
                int pos = sloppy_block_function->position();
                bool ok = true;
                bool was_added;
                auto declaration = factory->NewVariableDeclaration(pos);
                // Based on the preceding checks, it doesn't matter what we pass as
                // sloppy_mode_block_scope_function_redefinition.
                Variable* var = DeclareVariable(
                    declaration, name, pos, VariableMode::kVar, NORMAL_VARIABLE,
                    Variable::DefaultInitializationFlag(VariableMode::kVar), &was_added,
                    nullptr, &ok);
                DCHECK(ok);
                VariableProxy* source = factory->NewVariableProxy(sloppy_block_function->var());
                VariableProxy* target = factory->NewVariableProxy(var);
                Assignment* assignment = factory->NewAssignment(
                    sloppy_block_function->init(), target, source, pos);
                assignment->set_lookup_hoisting_mode(LookupHoistingMode::kLegacySloppy);
                Statement* statement = factory->NewExpressionStatement(assignment, pos);
                sloppy_block_function->set_statement(statement);
            } else {
                DCHECK(is_being_lazily_parsed_);
                bool was_added;
                Variable* var = DeclareVariableName(name, VariableMode::kVar, &was_added);
                if (sloppy_block_function->init() == Token::ASSIGN)
                    var->set_maybe_assigned();
            }
        }
    }

    bool DeclarationScope::Analyze(ParseInfo* info)
    {
        RuntimeCallTimerScope runtimeTimer(
            info->runtime_call_stats(),
            info->on_background_thread()
                ? RuntimeCallCounterId::kCompileBackgroundScopeAnalysis
                : RuntimeCallCounterId::kCompileScopeAnalysis);
        DCHECK_NOT_NULL(info->literal());
        DeclarationScope* scope = info->literal()->scope();

        base::Optional<AllowHandleDereference> allow_deref;
        if (!info->maybe_outer_scope_info().is_null()) {
            // Allow dereferences to the scope info if there is one.
            allow_deref.emplace();
        }

        if (scope->is_eval_scope() && is_sloppy(scope->language_mode())) {
            AstNodeFactory factory(info->ast_value_factory(), info->zone());
            scope->HoistSloppyBlockFunctions(&factory);
        }

        // We are compiling one of four cases:
        // 1) top-level code,
        // 2) a function/eval/module on the top-level
        // 3) a function/eval in a scope that was already resolved.
        DCHECK(scope->is_script_scope() || scope->outer_scope()->is_script_scope() || scope->outer_scope()->already_resolved_);

        // The outer scope is never lazy.
        scope->set_should_eager_compile();

        if (scope->must_use_preparsed_scope_data_) {
            DCHECK_EQ(scope->scope_type_, ScopeType::FUNCTION_SCOPE);
            allow_deref.emplace();
            info->consumed_preparse_data()->RestoreScopeAllocationData(scope);
        }

        if (!scope->AllocateVariables(info))
            return false;

#ifdef DEBUG
        if (FLAG_print_scopes) {
            PrintF("Global scope:\n");
            scope->Print();
        }
        scope->CheckScopePositions();
        scope->CheckZones();
#endif

        return true;
    }

    void DeclarationScope::DeclareThis(AstValueFactory* ast_value_factory)
    {
        DCHECK(has_this_declaration());

        bool derived_constructor = IsDerivedConstructor(function_kind_);

        receiver_ = new (zone())
            Variable(this, ast_value_factory->this_string(),
                derived_constructor ? VariableMode::kConst : VariableMode::kVar,
                THIS_VARIABLE,
                derived_constructor ? kNeedsInitialization : kCreatedInitialized,
                kNotAssigned);
    }

    void DeclarationScope::DeclareArguments(AstValueFactory* ast_value_factory)
    {
        DCHECK(is_function_scope());
        DCHECK(!is_arrow_scope());

        // Declare 'arguments' variable which exists in all non arrow functions.  Note
        // that it might never be accessed, in which case it won't be allocated during
        // variable allocation.
        bool was_added;
        arguments_ = Declare(zone(), ast_value_factory->arguments_string(), VariableMode::kVar,
            NORMAL_VARIABLE, kCreatedInitialized, kNotAssigned, &was_added);
        if (!was_added && IsLexicalVariableMode(arguments_->mode())) {
            // Check if there's lexically declared variable named arguments to avoid
            // redeclaration. See ES#sec-functiondeclarationinstantiation, step 20.
            arguments_ = nullptr;
        }
    }

    void DeclarationScope::DeclareDefaultFunctionVariables(
        AstValueFactory* ast_value_factory)
    {
        DCHECK(is_function_scope());
        DCHECK(!is_arrow_scope());

        DeclareThis(ast_value_factory);
        bool was_added;
        new_target_ = Declare(zone(), ast_value_factory->new_target_string(),
            VariableMode::kConst, NORMAL_VARIABLE,
            kCreatedInitialized, kNotAssigned, &was_added);
        DCHECK(was_added);

        if (IsConciseMethod(function_kind_) || IsClassConstructor(function_kind_) || IsAccessorFunction(function_kind_)) {
            EnsureRareData()->this_function = Declare(
                zone(), ast_value_factory->this_function_string(), VariableMode::kConst,
                NORMAL_VARIABLE, kCreatedInitialized, kNotAssigned, &was_added);
            DCHECK(was_added);
        }
    }

    Variable* DeclarationScope::DeclareFunctionVar(const AstRawString* name,
        Scope* cache)
    {
        DCHECK(is_function_scope());
        DCHECK_NULL(function_);
        if (cache == nullptr)
            cache = this;
        DCHECK_NULL(cache->variables_.Lookup(name));
        VariableKind kind = is_sloppy(language_mode()) ? SLOPPY_FUNCTION_NAME_VARIABLE
                                                       : NORMAL_VARIABLE;
        function_ = new (zone())
            Variable(this, name, VariableMode::kConst, kind, kCreatedInitialized);
        if (calls_sloppy_eval()) {
            cache->NonLocal(name, VariableMode::kDynamic);
        } else {
            cache->variables_.Add(zone(), function_);
        }
        return function_;
    }

    Variable* DeclarationScope::DeclareGeneratorObjectVar(
        const AstRawString* name)
    {
        DCHECK(is_function_scope() || is_module_scope());
        DCHECK_NULL(generator_object_var());

        Variable* result = EnsureRareData()->generator_object = NewTemporary(name, kNotAssigned);
        result->set_is_used();
        return result;
    }

    Scope* Scope::FinalizeBlockScope()
    {
        DCHECK(is_block_scope());
#ifdef DEBUG
        DCHECK_NE(sibling_, this);
#endif

        if (variables_.occupancy() > 0 || (is_declaration_scope() && AsDeclarationScope()->calls_sloppy_eval())) {
            return this;
        }

        DCHECK(!is_class_scope());

        // Remove this scope from outer scope.
        outer_scope()->RemoveInnerScope(this);

        // Reparent inner scopes.
        if (inner_scope_ != nullptr) {
            Scope* scope = inner_scope_;
            scope->outer_scope_ = outer_scope();
            while (scope->sibling_ != nullptr) {
                scope = scope->sibling_;
                scope->outer_scope_ = outer_scope();
            }
            scope->sibling_ = outer_scope()->inner_scope_;
            outer_scope()->inner_scope_ = inner_scope_;
            inner_scope_ = nullptr;
        }

        // Move unresolved variables
        if (!unresolved_list_.is_empty()) {
            outer_scope()->unresolved_list_.Prepend(std::move(unresolved_list_));
            unresolved_list_.Clear();
        }

        if (inner_scope_calls_eval_)
            outer_scope()->inner_scope_calls_eval_ = true;

        // No need to propagate scope_calls_eval_, since if it was relevant to
        // this scope we would have had to bail out at the top.
        DCHECK(!scope_calls_eval_ || !is_declaration_scope() || !is_sloppy(language_mode()));

        // This block does not need a context.
        num_heap_slots_ = 0;

        // Mark scope as removed by making it its own sibling.
#ifdef DEBUG
        sibling_ = this;
#endif

        return nullptr;
    }

    void DeclarationScope::AddLocal(Variable* var)
    {
        DCHECK(!already_resolved_);
        // Temporaries are only placed in ClosureScopes.
        DCHECK_EQ(GetClosureScope(), this);
        locals_.Add(var);
    }

    void Scope::Snapshot::Reparent(DeclarationScope* new_parent)
    {
        DCHECK(!IsCleared());
        DCHECK_EQ(new_parent, outer_scope_and_calls_eval_.GetPointer()->inner_scope_);
        DCHECK_EQ(new_parent->outer_scope_, outer_scope_and_calls_eval_.GetPointer());
        DCHECK_EQ(new_parent, new_parent->GetClosureScope());
        DCHECK_NULL(new_parent->inner_scope_);
        DCHECK(new_parent->unresolved_list_.is_empty());
        Scope* inner_scope = new_parent->sibling_;
        if (inner_scope != top_inner_scope_) {
            for (; inner_scope->sibling() != top_inner_scope_;
                 inner_scope = inner_scope->sibling()) {
                inner_scope->outer_scope_ = new_parent;
                if (inner_scope->inner_scope_calls_eval_) {
                    new_parent->inner_scope_calls_eval_ = true;
                }
                DCHECK_NE(inner_scope, new_parent);
            }
            inner_scope->outer_scope_ = new_parent;
            if (inner_scope->inner_scope_calls_eval_) {
                new_parent->inner_scope_calls_eval_ = true;
            }
            new_parent->inner_scope_ = new_parent->sibling_;
            inner_scope->sibling_ = nullptr;
            // Reset the sibling rather than the inner_scope_ since we
            // want to keep new_parent there.
            new_parent->sibling_ = top_inner_scope_;
        }

        Scope* outer_scope_ = outer_scope_and_calls_eval_.GetPointer();
        new_parent->unresolved_list_.MoveTail(&outer_scope_->unresolved_list_,
            top_unresolved_);

        // Move temporaries allocated for complex parameter initializers.
        DeclarationScope* outer_closure = outer_scope_->GetClosureScope();
        for (auto it = top_local_; it != outer_closure->locals()->end(); ++it) {
            Variable* local = *it;
            DCHECK_EQ(VariableMode::kTemporary, local->mode());
            DCHECK_EQ(local->scope(), local->scope()->GetClosureScope());
            DCHECK_NE(local->scope(), new_parent);
            local->set_scope(new_parent);
        }
        new_parent->locals_.MoveTail(outer_closure->locals(), top_local_);
        outer_closure->locals_.Rewind(top_local_);

        // Move eval calls since Snapshot's creation into new_parent.
        if (outer_scope_and_calls_eval_->scope_calls_eval_) {
            new_parent->scope_calls_eval_ = true;
            new_parent->inner_scope_calls_eval_ = true;
        }

        // We are in the arrow function case. The calls eval we may have recorded
        // is intended for the inner scope and we should simply restore the
        // original "calls eval" flag of the outer scope.
        RestoreEvalFlag();
        Clear();
    }

    void Scope::ReplaceOuterScope(Scope* outer)
    {
        DCHECK_NOT_NULL(outer);
        DCHECK_NOT_NULL(outer_scope_);
        DCHECK(!already_resolved_);
        outer_scope_->RemoveInnerScope(this);
        outer->AddInnerScope(this);
        outer_scope_ = outer;
    }

    Variable* Scope::LookupInScopeInfo(const AstRawString* name, Scope* cache)
    {
        DCHECK(!scope_info_.is_null());
        DCHECK_NULL(cache->variables_.Lookup(name));
        DisallowHeapAllocation no_gc;

        String name_handle = *name->string();
        // The Scope is backed up by ScopeInfo. This means it cannot operate in a
        // heap-independent mode, and all strings must be internalized immediately. So
        // it's ok to get the Handle<String> here.
        bool found = false;

        VariableLocation location;
        int index;
        VariableMode mode;
        InitializationFlag init_flag;
        MaybeAssignedFlag maybe_assigned_flag;

        {
            location = VariableLocation::CONTEXT;
            index = ScopeInfo::ContextSlotIndex(*scope_info_, name_handle, &mode,
                &init_flag, &maybe_assigned_flag);
            found = index >= 0;
        }

        if (!found && is_module_scope()) {
            location = VariableLocation::MODULE;
            index = scope_info_->ModuleIndex(name_handle, &mode, &init_flag,
                &maybe_assigned_flag);
            found = index != 0;
        }

        if (!found) {
            index = scope_info_->FunctionContextSlotIndex(name_handle);
            if (index < 0)
                return nullptr; // Nowhere found.
            Variable* var = AsDeclarationScope()->DeclareFunctionVar(name, cache);
            DCHECK_EQ(VariableMode::kConst, var->mode());
            var->AllocateTo(VariableLocation::CONTEXT, index);
            return cache->variables_.Lookup(name);
        }

        if (!is_module_scope()) {
            DCHECK_NE(index, scope_info_->ReceiverContextSlotIndex());
        }

        bool was_added;
        Variable* var = cache->variables_.Declare(zone(), this, name, mode, NORMAL_VARIABLE,
            init_flag, maybe_assigned_flag, &was_added);
        DCHECK(was_added);
        var->AllocateTo(location, index);
        return var;
    }

    Variable* DeclarationScope::DeclareParameter(const AstRawString* name,
        VariableMode mode,
        bool is_optional, bool is_rest,
        AstValueFactory* ast_value_factory,
        int position)
    {
        DCHECK(!already_resolved_);
        DCHECK(is_function_scope() || is_module_scope());
        DCHECK(!has_rest_);
        DCHECK(!is_optional || !is_rest);
        DCHECK(!is_being_lazily_parsed_);
        DCHECK(!was_lazily_parsed_);
        Variable* var;
        if (mode == VariableMode::kTemporary) {
            var = NewTemporary(name);
        } else {
            var = LookupLocal(name);
            DCHECK_EQ(mode, VariableMode::kVar);
            DCHECK(var->is_parameter());
        }
        has_rest_ = is_rest;
        var->set_initializer_position(position);
        params_.Add(var, zone());
        if (!is_rest)
            ++num_parameters_;
        if (name == ast_value_factory->arguments_string()) {
            has_arguments_parameter_ = true;
        }
        // Params are automatically marked as used to make sure that the debugger and
        // function.arguments sees them.
        // TODO(verwaest): Reevaluate whether we always need to do this, since
        // strict-mode function.arguments does not make the arguments available.
        var->set_is_used();
        return var;
    }

    void DeclarationScope::RecordParameter(bool is_rest)
    {
        DCHECK(!already_resolved_);
        DCHECK(is_function_scope() || is_module_scope());
        DCHECK(is_being_lazily_parsed_);
        DCHECK(!has_rest_);
        has_rest_ = is_rest;
        if (!is_rest)
            ++num_parameters_;
    }

    Variable* Scope::DeclareLocal(const AstRawString* name, VariableMode mode,
        VariableKind kind, bool* was_added,
        InitializationFlag init_flag)
    {
        DCHECK(!already_resolved_);
        // This function handles VariableMode::kVar, VariableMode::kLet, and
        // VariableMode::kConst modes.  VariableMode::kDynamic variables are
        // introduced during variable allocation, and VariableMode::kTemporary
        // variables are allocated via NewTemporary().
        DCHECK(IsDeclaredVariableMode(mode));
        DCHECK_IMPLIES(GetDeclarationScope()->is_being_lazily_parsed(),
            mode == VariableMode::kVar || mode == VariableMode::kLet || mode == VariableMode::kConst);
        DCHECK(!GetDeclarationScope()->was_lazily_parsed());
        Variable* var = Declare(zone(), name, mode, kind, init_flag, kNotAssigned, was_added);

        // Pessimistically assume that top-level variables will be assigned and used.
        //
        // Top-level variables in a script can be accessed by other scripts or even
        // become global properties. While this does not apply to top-level variables
        // in a module (assuming they are not exported), we must still mark these as
        // assigned because they might be accessed by a lazily parsed top-level
        // function, which, for efficiency, we preparse without variable tracking.
        if (is_script_scope() || is_module_scope()) {
            if (mode != VariableMode::kConst)
                var->set_maybe_assigned();
            var->set_is_used();
        }

        return var;
    }

    Variable* Scope::DeclareVariable(
        Declaration* declaration, const AstRawString* name, int pos,
        VariableMode mode, VariableKind kind, InitializationFlag init,
        bool* was_added, bool* sloppy_mode_block_scope_function_redefinition,
        bool* ok)
    {
        DCHECK(IsDeclaredVariableMode(mode));
        DCHECK(!already_resolved_);
        DCHECK(!GetDeclarationScope()->is_being_lazily_parsed());
        DCHECK(!GetDeclarationScope()->was_lazily_parsed());

        if (mode == VariableMode::kVar && !is_declaration_scope()) {
            return GetDeclarationScope()->DeclareVariable(
                declaration, name, pos, mode, kind, init, was_added,
                sloppy_mode_block_scope_function_redefinition, ok);
        }
        DCHECK(!is_catch_scope());
        DCHECK(!is_with_scope());
        DCHECK(is_declaration_scope() || (IsLexicalVariableMode(mode) && is_block_scope()));

        DCHECK_NOT_NULL(name);

        Variable* var = LookupLocal(name);
        // Declare the variable in the declaration scope.
        *was_added = var == nullptr;
        if (V8_LIKELY(*was_added)) {
            if (V8_UNLIKELY(is_eval_scope() && is_sloppy(language_mode()) && mode == VariableMode::kVar)) {
                // In a var binding in a sloppy direct eval, pollute the enclosing scope
                // with this new binding by doing the following:
                // The proxy is bound to a lookup variable to force a dynamic declaration
                // using the DeclareEvalVar or DeclareEvalFunction runtime functions.
                DCHECK_EQ(NORMAL_VARIABLE, kind);
                var = NonLocal(name, VariableMode::kDynamic);
                // Mark the var as used in case anyone outside the eval wants to use it.
                var->set_is_used();
            } else {
                // Declare the name.
                var = DeclareLocal(name, mode, kind, was_added, init);
                DCHECK(*was_added);
            }
        } else {
            var->set_maybe_assigned();
            if (V8_UNLIKELY(IsLexicalVariableMode(mode) || IsLexicalVariableMode(var->mode()))) {
                // The name was declared in this scope before; check for conflicting
                // re-declarations. We have a conflict if either of the declarations is
                // not a var (in script scope, we also have to ignore legacy const for
                // compatibility). There is similar code in runtime.cc in the Declare
                // functions. The function CheckConflictingVarDeclarations checks for
                // var and let bindings from different scopes whereas this is a check
                // for conflicting declarations within the same scope. This check also
                // covers the special case
                //
                // function () { let x; { var x; } }
                //
                // because the var declaration is hoisted to the function scope where
                // 'x' is already bound.
                //
                // In harmony we treat re-declarations as early errors. See ES5 16 for a
                // definition of early errors.
                //
                // Allow duplicate function decls for web compat, see bug 4693.
                *ok = var->is_sloppy_block_function() && kind == SLOPPY_BLOCK_FUNCTION_VARIABLE;
                *sloppy_mode_block_scope_function_redefinition = *ok;
            }
        }
        DCHECK_NOT_NULL(var);

        // We add a declaration node for every declaration. The compiler
        // will only generate code if necessary. In particular, declarations
        // for inner local variables that do not represent functions won't
        // result in any generated code.
        //
        // This will lead to multiple declaration nodes for the
        // same variable if it is declared several times. This is not a
        // semantic issue, but it may be a performance issue since it may
        // lead to repeated DeclareEvalVar or DeclareEvalFunction calls.
        decls_.Add(declaration);
        declaration->set_var(var);
        return var;
    }

    Variable* Scope::DeclareVariableName(const AstRawString* name,
        VariableMode mode, bool* was_added,
        VariableKind kind)
    {
        DCHECK(IsDeclaredVariableMode(mode));
        DCHECK(!already_resolved_);
        DCHECK(GetDeclarationScope()->is_being_lazily_parsed());

        if (mode == VariableMode::kVar && !is_declaration_scope()) {
            return GetDeclarationScope()->DeclareVariableName(name, mode, was_added,
                kind);
        }
        DCHECK(!is_with_scope());
        DCHECK(!is_eval_scope());
        DCHECK(is_declaration_scope() || IsLexicalVariableMode(mode));
        DCHECK(scope_info_.is_null());

        // Declare the variable in the declaration scope.
        Variable* var = DeclareLocal(name, mode, kind, was_added);
        if (!*was_added) {
            if (IsLexicalVariableMode(mode) || IsLexicalVariableMode(var->mode())) {
                if (!var->is_sloppy_block_function() || kind != SLOPPY_BLOCK_FUNCTION_VARIABLE) {
                    // Duplicate functions are allowed in the sloppy mode, but if this is
                    // not a function declaration, it's an error. This is an error PreParser
                    // hasn't previously detected.
                    return nullptr;
                }
                // Sloppy block function redefinition.
            }
            var->set_maybe_assigned();
        }
        var->set_is_used();
        return var;
    }

    Variable* Scope::DeclareCatchVariableName(const AstRawString* name)
    {
        DCHECK(!already_resolved_);
        DCHECK(is_catch_scope());
        DCHECK(scope_info_.is_null());

        bool was_added;
        Variable* result = Declare(zone(), name, VariableMode::kVar, NORMAL_VARIABLE,
            kCreatedInitialized, kNotAssigned, &was_added);
        DCHECK(was_added);
        return result;
    }

    void Scope::AddUnresolved(VariableProxy* proxy)
    {
        DCHECK(!already_resolved_);
        DCHECK(!proxy->is_resolved());
        unresolved_list_.Add(proxy);
    }

    Variable* DeclarationScope::DeclareDynamicGlobal(const AstRawString* name,
        VariableKind kind,
        Scope* cache)
    {
        DCHECK(is_script_scope());
        bool was_added;
        return cache->variables_.Declare(
            zone(), this, name, VariableMode::kDynamicGlobal, kind,
            kCreatedInitialized, kNotAssigned, &was_added);
        // TODO(neis): Mark variable as maybe-assigned?
    }

    bool Scope::RemoveUnresolved(VariableProxy* var)
    {
        return unresolved_list_.Remove(var);
    }

    void Scope::DeleteUnresolved(VariableProxy* var)
    {
        DCHECK(unresolved_list_.Contains(var));
        var->mark_removed_from_unresolved();
    }

    Variable* Scope::NewTemporary(const AstRawString* name)
    {
        return NewTemporary(name, kMaybeAssigned);
    }

    Variable* Scope::NewTemporary(const AstRawString* name,
        MaybeAssignedFlag maybe_assigned)
    {
        DeclarationScope* scope = GetClosureScope();
        Variable* var = new (zone()) Variable(scope, name, VariableMode::kTemporary,
            NORMAL_VARIABLE, kCreatedInitialized);
        scope->AddLocal(var);
        if (maybe_assigned == kMaybeAssigned)
            var->set_maybe_assigned();
        return var;
    }

    Declaration* DeclarationScope::CheckConflictingVarDeclarations()
    {
        if (has_checked_syntax_)
            return nullptr;
        for (Declaration* decl : decls_) {
            // Lexical vs lexical conflicts within the same scope have already been
            // captured in Parser::Declare. The only conflicts we still need to check
            // are lexical vs nested var.
            if (decl->IsVariableDeclaration() && decl->AsVariableDeclaration()->AsNested() != nullptr) {
                Scope* current = decl->AsVariableDeclaration()->AsNested()->scope();
                DCHECK(decl->var()->mode() == VariableMode::kVar || decl->var()->mode() == VariableMode::kDynamic);
                // Iterate through all scopes until the declaration scope.
                do {
                    // There is a conflict if there exists a non-VAR binding.
                    if (current->is_catch_scope()) {
                        current = current->outer_scope();
                        continue;
                    }
                    Variable* other_var = current->LookupLocal(decl->var()->raw_name());
                    if (other_var != nullptr) {
                        DCHECK(IsLexicalVariableMode(other_var->mode()));
                        return decl;
                    }
                    current = current->outer_scope();
                } while (current != this);
            }
        }

        if (V8_LIKELY(!is_eval_scope()))
            return nullptr;
        if (!is_sloppy(language_mode()))
            return nullptr;

        // Var declarations in sloppy eval are hoisted to the first non-eval
        // declaration scope. Check for conflicts between the eval scope that
        // declaration scope.
        Scope* end = this;
        do {
            end = end->outer_scope_->GetDeclarationScope();
        } while (end->is_eval_scope());
        end = end->outer_scope_;

        for (Declaration* decl : decls_) {
            if (IsLexicalVariableMode(decl->var()->mode()))
                continue;
            Scope* current = outer_scope_;
            // Iterate through all scopes until and including the declaration scope.
            do {
                // There is a conflict if there exists a non-VAR binding up to the
                // declaration scope in which this sloppy-eval runs.
                Variable* other_var = current->LookupInScopeOrScopeInfo(decl->var()->raw_name());
                if (other_var != nullptr && IsLexicalVariableMode(other_var->mode())) {
                    DCHECK(!current->is_catch_scope());
                    return decl;
                }
                current = current->outer_scope();
            } while (current != end);
        }
        return nullptr;
    }

    const AstRawString* Scope::FindVariableDeclaredIn(Scope* scope,
        VariableMode mode_limit)
    {
        const VariableMap& variables = scope->variables_;
        for (ZoneHashMap::Entry* p = variables.Start(); p != nullptr;
             p = variables.Next(p)) {
            const AstRawString* name = static_cast<const AstRawString*>(p->key);
            Variable* var = LookupLocal(name);
            if (var != nullptr && var->mode() <= mode_limit)
                return name;
        }
        return nullptr;
    }

    void DeclarationScope::DeserializeReceiver(AstValueFactory* ast_value_factory)
    {
        if (is_script_scope()) {
            DCHECK_NOT_NULL(receiver_);
            return;
        }
        DCHECK(has_this_declaration());
        DeclareThis(ast_value_factory);
        if (is_debug_evaluate_scope()) {
            receiver_->AllocateTo(VariableLocation::LOOKUP, -1);
        } else {
            receiver_->AllocateTo(VariableLocation::CONTEXT,
                scope_info_->ReceiverContextSlotIndex());
        }
    }

    bool DeclarationScope::AllocateVariables(ParseInfo* info)
    {
        // Module variables must be allocated before variable resolution
        // to ensure that UpdateNeedsHoleCheck() can detect import variables.
        if (is_module_scope())
            AsModuleScope()->AllocateModuleVariables();

        ClassScope* closest_class_scope = GetClassScope();
        if (closest_class_scope != nullptr && !closest_class_scope->ResolvePrivateNames(info)) {
            DCHECK(info->pending_error_handler()->has_pending_error());
            return false;
        }

        if (!ResolveVariablesRecursively(info)) {
            DCHECK(info->pending_error_handler()->has_pending_error());
            return false;
        }

        // // Don't allocate variables of preparsed scopes.
        if (!was_lazily_parsed())
            AllocateVariablesRecursively();

        return true;
    }

    bool Scope::HasThisReference() const
    {
        if (is_declaration_scope() && AsDeclarationScope()->has_this_reference()) {
            return true;
        }

        for (Scope* scope = inner_scope_; scope != nullptr; scope = scope->sibling_) {
            if (!scope->is_declaration_scope() || !scope->AsDeclarationScope()->has_this_declaration()) {
                if (scope->HasThisReference())
                    return true;
            }
        }

        return false;
    }

    bool Scope::AllowsLazyParsingWithoutUnresolvedVariables(
        const Scope* outer) const
    {
        // If none of the outer scopes need to decide whether to context allocate
        // specific variables, we can preparse inner functions without unresolved
        // variables. Otherwise we need to find unresolved variables to force context
        // allocation of the matching declarations. We can stop at the outer scope for
        // the parse, since context allocation of those variables is already
        // guaranteed to be correct.
        for (const Scope* s = this; s != outer; s = s->outer_scope_) {
            // Eval forces context allocation on all outer scopes, so we don't need to
            // look at those scopes. Sloppy eval makes top-level non-lexical variables
            // dynamic, whereas strict-mode requires context allocation.
            if (s->is_eval_scope())
                return is_sloppy(s->language_mode());
            // Catch scopes force context allocation of all variables.
            if (s->is_catch_scope())
                continue;
            // With scopes do not introduce variables that need allocation.
            if (s->is_with_scope())
                continue;
            DCHECK(s->is_module_scope() || s->is_block_scope() || s->is_function_scope());
            return false;
        }
        return true;
    }

    bool DeclarationScope::AllowsLazyCompilation() const
    {
        // Functions which force eager compilation and class member initializer
        // functions are not lazily compilable.
        return !force_eager_compilation_ && !IsClassMembersInitializerFunction(function_kind());
    }

    int Scope::ContextChainLength(Scope* scope) const
    {
        int n = 0;
        for (const Scope* s = this; s != scope; s = s->outer_scope_) {
            DCHECK_NOT_NULL(s); // scope must be in the scope chain
            if (s->NeedsContext())
                n++;
        }
        return n;
    }

    int Scope::ContextChainLengthUntilOutermostSloppyEval() const
    {
        int result = 0;
        int length = 0;

        for (const Scope* s = this; s != nullptr; s = s->outer_scope()) {
            if (!s->NeedsContext())
                continue;
            length++;
            if (s->is_declaration_scope() && s->AsDeclarationScope()->calls_sloppy_eval()) {
                result = length;
            }
        }

        return result;
    }

    ClassScope* Scope::GetClassScope()
    {
        Scope* scope = this;
        while (scope != nullptr && !scope->is_class_scope()) {
            scope = scope->outer_scope();
        }
        if (scope != nullptr && scope->is_class_scope()) {
            return scope->AsClassScope();
        }
        return nullptr;
    }

    DeclarationScope* Scope::GetDeclarationScope()
    {
        Scope* scope = this;
        while (!scope->is_declaration_scope()) {
            scope = scope->outer_scope();
        }
        return scope->AsDeclarationScope();
    }

    const DeclarationScope* Scope::GetClosureScope() const
    {
        const Scope* scope = this;
        while (!scope->is_declaration_scope() || scope->is_block_scope()) {
            scope = scope->outer_scope();
        }
        return scope->AsDeclarationScope();
    }

    DeclarationScope* Scope::GetClosureScope()
    {
        Scope* scope = this;
        while (!scope->is_declaration_scope() || scope->is_block_scope()) {
            scope = scope->outer_scope();
        }
        return scope->AsDeclarationScope();
    }

    bool Scope::NeedsScopeInfo() const
    {
        DCHECK(!already_resolved_);
        DCHECK(GetClosureScope()->ShouldEagerCompile());
        // The debugger expects all functions to have scope infos.
        // TODO(jochen|yangguo): Remove this requirement.
        if (is_function_scope())
            return true;
        return NeedsContext();
    }

    bool Scope::ShouldBanArguments()
    {
        return GetReceiverScope()->should_ban_arguments();
    }

    DeclarationScope* Scope::GetReceiverScope()
    {
        Scope* scope = this;
        while (!scope->is_declaration_scope() || (!scope->is_script_scope() && !scope->AsDeclarationScope()->has_this_declaration())) {
            scope = scope->outer_scope();
        }
        return scope->AsDeclarationScope();
    }

    Scope* Scope::GetOuterScopeWithContext()
    {
        Scope* scope = outer_scope_;
        while (scope && !scope->NeedsContext()) {
            scope = scope->outer_scope();
        }
        return scope;
    }

    namespace {
        bool WasLazilyParsed(Scope* scope)
        {
            return scope->is_declaration_scope() && scope->AsDeclarationScope()->was_lazily_parsed();
        }

    } // namespace

    template <typename FunctionType>
    void Scope::ForEach(FunctionType callback)
    {
        Scope* scope = this;
        while (true) {
            Iteration iteration = callback(scope);
            // Try to descend into inner scopes first.
            if ((iteration == Iteration::kDescend) && scope->inner_scope_ != nullptr) {
                scope = scope->inner_scope_;
            } else {
                // Find the next outer scope with a sibling.
                while (scope->sibling_ == nullptr) {
                    if (scope == this)
                        return;
                    scope = scope->outer_scope_;
                }
                if (scope == this)
                    return;
                scope = scope->sibling_;
            }
        }
    }

    void Scope::CollectNonLocals(DeclarationScope* max_outer_scope,
        Isolate* isolate, ParseInfo* info,
        Handle<StringSet>* non_locals)
    {
        this->ForEach([max_outer_scope, isolate, info, non_locals](Scope* scope) {
            // Module variables must be allocated before variable resolution
            // to ensure that UpdateNeedsHoleCheck() can detect import variables.
            if (scope->is_module_scope()) {
                scope->AsModuleScope()->AllocateModuleVariables();
            }

            // Lazy parsed declaration scopes are already partially analyzed. If there
            // are unresolved references remaining, they just need to be resolved in
            // outer scopes.
            Scope* lookup = WasLazilyParsed(scope) ? scope->outer_scope() : scope;

            for (VariableProxy* proxy : scope->unresolved_list_) {
                DCHECK(!proxy->is_resolved());
                Variable* var = Lookup<kParsedScope>(proxy, lookup, max_outer_scope->outer_scope());
                if (var == nullptr) {
                    *non_locals = StringSet::Add(isolate, *non_locals, proxy->name());
                } else {
                    // In this case we need to leave scopes in a way that they can be
                    // allocated. If we resolved variables from lazy parsed scopes, we need
                    // to context allocate the var.
                    scope->ResolveTo(info, proxy, var);
                    if (!var->is_dynamic() && lookup != scope)
                        var->ForceContextAllocation();
                }
            }

            // Clear unresolved_list_ as it's in an inconsistent state.
            scope->unresolved_list_.Clear();
            return Iteration::kDescend;
        });
    }

    void Scope::AnalyzePartially(DeclarationScope* max_outer_scope,
        AstNodeFactory* ast_node_factory,
        UnresolvedList* new_unresolved_list)
    {
        this->ForEach([max_outer_scope, ast_node_factory,
                          new_unresolved_list](Scope* scope) {
            DCHECK_IMPLIES(scope->is_declaration_scope(),
                !scope->AsDeclarationScope()->was_lazily_parsed());

            for (VariableProxy* proxy = scope->unresolved_list_.first();
                 proxy != nullptr; proxy = proxy->next_unresolved()) {
                DCHECK(!proxy->is_resolved());
                Variable* var = Lookup<kParsedScope>(proxy, scope, max_outer_scope->outer_scope());
                if (var == nullptr) {
                    // Don't copy unresolved references to the script scope, unless it's a
                    // reference to a private name or method. In that case keep it so we
                    // can fail later.
                    if (!max_outer_scope->outer_scope()->is_script_scope()) {
                        VariableProxy* copy = ast_node_factory->CopyVariableProxy(proxy);
                        new_unresolved_list->Add(copy);
                    }
                } else {
                    var->set_is_used();
                    if (proxy->is_assigned())
                        var->set_maybe_assigned();
                }
            }

            // Clear unresolved_list_ as it's in an inconsistent state.
            scope->unresolved_list_.Clear();
            return Iteration::kDescend;
        });
    }

    Handle<StringSet> DeclarationScope::CollectNonLocals(
        Isolate* isolate, ParseInfo* info, Handle<StringSet> non_locals)
    {
        Scope::CollectNonLocals(this, isolate, info, &non_locals);
        return non_locals;
    }

    void DeclarationScope::ResetAfterPreparsing(AstValueFactory* ast_value_factory,
        bool aborted)
    {
        DCHECK(is_function_scope());

        // Reset all non-trivial members.
        params_.Clear();
        decls_.Clear();
        locals_.Clear();
        inner_scope_ = nullptr;
        unresolved_list_.Clear();
        sloppy_block_functions_.Clear();
        rare_data_ = nullptr;
        has_rest_ = false;

        DCHECK_NE(zone_, ast_value_factory->zone());
        zone_->ReleaseMemory();

        if (aborted) {
            // Prepare scope for use in the outer zone.
            zone_ = ast_value_factory->zone();
            variables_.Reset(ZoneAllocationPolicy(zone_));
            if (!IsArrowFunction(function_kind_)) {
                has_simple_parameters_ = true;
                DeclareDefaultFunctionVariables(ast_value_factory);
            }
        } else {
            // Make sure this scope isn't used for allocation anymore.
            zone_ = nullptr;
            variables_.Invalidate();
        }

#ifdef DEBUG
        needs_migration_ = false;
        is_being_lazily_parsed_ = false;
#endif

        was_lazily_parsed_ = !aborted;
    }

    bool Scope::IsSkippableFunctionScope()
    {
        // Lazy non-arrow function scopes are skippable. Lazy functions are exactly
        // those Scopes which have their own PreparseDataBuilder object. This
        // logic ensures that the scope allocation data is consistent with the
        // skippable function data (both agree on where the lazy function boundaries
        // are).
        if (!is_function_scope())
            return false;
        DeclarationScope* declaration_scope = AsDeclarationScope();
        return !declaration_scope->is_arrow_scope() && declaration_scope->preparse_data_builder() != nullptr;
    }

    void Scope::SavePreparseData(Parser* parser)
    {
        this->ForEach([parser](Scope* scope) {
            if (scope->IsSkippableFunctionScope()) {
                scope->AsDeclarationScope()->SavePreparseDataForDeclarationScope(parser);
            }
            return Iteration::kDescend;
        });
    }

    void DeclarationScope::SavePreparseDataForDeclarationScope(Parser* parser)
    {
        if (preparse_data_builder_ == nullptr)
            return;
        preparse_data_builder_->SaveScopeAllocationData(this, parser);
    }

    void DeclarationScope::AnalyzePartially(Parser* parser,
        AstNodeFactory* ast_node_factory)
    {
        DCHECK(!force_eager_compilation_);
        UnresolvedList new_unresolved_list;
        if (!IsArrowFunction(function_kind_) && (!outer_scope_->is_script_scope() || (preparse_data_builder_ != nullptr && preparse_data_builder_->HasInnerFunctions()))) {
            // Try to resolve unresolved variables for this Scope and migrate those
            // which cannot be resolved inside. It doesn't make sense to try to resolve
            // them in the outer Scopes here, because they are incomplete.
            Scope::AnalyzePartially(this, ast_node_factory, &new_unresolved_list);

            // Migrate function_ to the right Zone.
            if (function_ != nullptr) {
                function_ = ast_node_factory->CopyVariable(function_);
            }

            SavePreparseData(parser);
        }

#ifdef DEBUG
        if (FLAG_print_scopes) {
            PrintF("Inner function scope:\n");
            Print();
        }
#endif

        ResetAfterPreparsing(ast_node_factory->ast_value_factory(), false);

        unresolved_list_ = std::move(new_unresolved_list);
    }

#ifdef DEBUG
    namespace {

        const char* Header(ScopeType scope_type, FunctionKind function_kind,
            bool is_declaration_scope)
        {
            switch (scope_type) {
            case EVAL_SCOPE:
                return "eval";
            // TODO(adamk): Should we print concise method scopes specially?
            case FUNCTION_SCOPE:
                if (IsGeneratorFunction(function_kind))
                    return "function*";
                if (IsAsyncFunction(function_kind))
                    return "async function";
                if (IsArrowFunction(function_kind))
                    return "arrow";
                return "function";
            case MODULE_SCOPE:
                return "module";
            case SCRIPT_SCOPE:
                return "global";
            case CATCH_SCOPE:
                return "catch";
            case BLOCK_SCOPE:
                return is_declaration_scope ? "varblock" : "block";
            case CLASS_SCOPE:
                return "class";
            case WITH_SCOPE:
                return "with";
            }
            UNREACHABLE();
        }

        void Indent(int n, const char* str) { PrintF("%*s%s", n, "", str); }

        void PrintName(const AstRawString* name)
        {
            PrintF("%.*s", name->length(), name->raw_data());
        }

        void PrintLocation(Variable* var)
        {
            switch (var->location()) {
            case VariableLocation::UNALLOCATED:
                break;
            case VariableLocation::PARAMETER:
                PrintF("parameter[%d]", var->index());
                break;
            case VariableLocation::LOCAL:
                PrintF("local[%d]", var->index());
                break;
            case VariableLocation::CONTEXT:
                PrintF("context[%d]", var->index());
                break;
            case VariableLocation::LOOKUP:
                PrintF("lookup");
                break;
            case VariableLocation::MODULE:
                PrintF("module");
                break;
            }
        }

        void PrintVar(int indent, Variable* var)
        {
            Indent(indent, VariableMode2String(var->mode()));
            PrintF(" ");
            if (var->raw_name()->IsEmpty())
                PrintF(".%p", reinterpret_cast<void*>(var));
            else
                PrintName(var->raw_name());
            PrintF(";  // (%p) ", reinterpret_cast<void*>(var));
            PrintLocation(var);
            bool comma = !var->IsUnallocated();
            if (var->has_forced_context_allocation()) {
                if (comma)
                    PrintF(", ");
                PrintF("forced context allocation");
                comma = true;
            }
            if (var->maybe_assigned() == kNotAssigned) {
                if (comma)
                    PrintF(", ");
                PrintF("never assigned");
                comma = true;
            }
            if (var->initialization_flag() == kNeedsInitialization && !var->binding_needs_init()) {
                if (comma)
                    PrintF(", ");
                PrintF("hole initialization elided");
            }
            PrintF("\n");
        }

        void PrintMap(int indent, const char* label, VariableMap* map, bool locals,
            Variable* function_var)
        {
            bool printed_label = false;
            for (VariableMap::Entry* p = map->Start(); p != nullptr; p = map->Next(p)) {
                Variable* var = reinterpret_cast<Variable*>(p->value);
                if (var == function_var)
                    continue;
                bool local = !IsDynamicVariableMode(var->mode());
                if ((locals ? local : !local) && (var->is_used() || !var->IsUnallocated())) {
                    if (!printed_label) {
                        Indent(indent, label);
                        printed_label = true;
                    }
                    PrintVar(indent, var);
                }
            }
        }

    } // anonymous namespace

    void DeclarationScope::PrintParameters()
    {
        PrintF(" (");
        for (int i = 0; i < params_.length(); i++) {
            if (i > 0)
                PrintF(", ");
            const AstRawString* name = params_[i]->raw_name();
            if (name->IsEmpty()) {
                PrintF(".%p", reinterpret_cast<void*>(params_[i]));
            } else {
                PrintName(name);
            }
        }
        PrintF(")");
    }

    void Scope::Print(int n)
    {
        int n0 = (n > 0 ? n : 0);
        int n1 = n0 + 2; // indentation

        // Print header.
        FunctionKind function_kind = is_function_scope()
            ? AsDeclarationScope()->function_kind()
            : kNormalFunction;
        Indent(n0, Header(scope_type_, function_kind, is_declaration_scope()));
        if (scope_name_ != nullptr && !scope_name_->IsEmpty()) {
            PrintF(" ");
            PrintName(scope_name_);
        }

        // Print parameters, if any.
        Variable* function = nullptr;
        if (is_function_scope()) {
            AsDeclarationScope()->PrintParameters();
            function = AsDeclarationScope()->function_var();
        }

        PrintF(" { // (%p) (%d, %d)\n", reinterpret_cast<void*>(this),
            start_position(), end_position());
        if (is_hidden()) {
            Indent(n1, "// is hidden\n");
        }

        // Function name, if any (named function literals, only).
        if (function != nullptr) {
            Indent(n1, "// (local) function name: ");
            PrintName(function->raw_name());
            PrintF("\n");
        }

        // Scope info.
        if (is_strict(language_mode())) {
            Indent(n1, "// strict mode scope\n");
        }
        if (IsAsmModule())
            Indent(n1, "// scope is an asm module\n");
        if (is_declaration_scope() && AsDeclarationScope()->calls_sloppy_eval()) {
            Indent(n1, "// scope calls sloppy 'eval'\n");
        }
        if (is_declaration_scope() && AsDeclarationScope()->NeedsHomeObject()) {
            Indent(n1, "// scope needs home object\n");
        }
        if (inner_scope_calls_eval_)
            Indent(n1, "// inner scope calls 'eval'\n");
        if (is_declaration_scope()) {
            DeclarationScope* scope = AsDeclarationScope();
            if (scope->was_lazily_parsed())
                Indent(n1, "// lazily parsed\n");
            if (scope->ShouldEagerCompile())
                Indent(n1, "// will be compiled\n");
        }
        if (num_stack_slots_ > 0) {
            Indent(n1, "// ");
            PrintF("%d stack slots\n", num_stack_slots_);
        }
        if (num_heap_slots_ > 0) {
            Indent(n1, "// ");
            PrintF("%d heap slots\n", num_heap_slots_);
        }

        // Print locals.
        if (function != nullptr) {
            Indent(n1, "// function var:\n");
            PrintVar(n1, function);
        }

        // Print temporaries.
        {
            bool printed_header = false;
            for (Variable* local : locals_) {
                if (local->mode() != VariableMode::kTemporary)
                    continue;
                if (!printed_header) {
                    printed_header = true;
                    Indent(n1, "// temporary vars:\n");
                }
                PrintVar(n1, local);
            }
        }

        if (variables_.occupancy() > 0) {
            PrintMap(n1, "// local vars:\n", &variables_, true, function);
            PrintMap(n1, "// dynamic vars:\n", &variables_, false, function);
        }

        if (is_class_scope()) {
            ClassScope* class_scope = AsClassScope();
            if (class_scope->rare_data_ != nullptr) {
                PrintMap(n1, "// private name vars:\n",
                    &(class_scope->rare_data_->private_name_map), true, function);
            }
        }

        // Print inner scopes (disable by providing negative n).
        if (n >= 0) {
            for (Scope* scope = inner_scope_; scope != nullptr;
                 scope = scope->sibling_) {
                PrintF("\n");
                scope->Print(n1);
            }
        }

        Indent(n0, "}\n");
    }

    void Scope::CheckScopePositions()
    {
        this->ForEach([](Scope* scope) {
            // Visible leaf scopes must have real positions.
            if (!scope->is_hidden() && scope->inner_scope_ == nullptr) {
                DCHECK_NE(kNoSourcePosition, scope->start_position());
                DCHECK_NE(kNoSourcePosition, scope->end_position());
            }
            return Iteration::kDescend;
        });
    }

    void Scope::CheckZones()
    {
        DCHECK(!needs_migration_);
        this->ForEach([](Scope* scope) {
            if (WasLazilyParsed(scope)) {
                DCHECK_NULL(scope->zone());
                DCHECK_NULL(scope->inner_scope_);
                return Iteration::kContinue;
            }
            return Iteration::kDescend;
        });
    }
#endif // DEBUG

    Variable* Scope::NonLocal(const AstRawString* name, VariableMode mode)
    {
        // Declare a new non-local.
        DCHECK(IsDynamicVariableMode(mode));
        bool was_added;
        Variable* var = variables_.Declare(zone(), this, name, mode, NORMAL_VARIABLE,
            kCreatedInitialized, kNotAssigned, &was_added);
        // Allocate it by giving it a dynamic lookup.
        var->AllocateTo(VariableLocation::LOOKUP, -1);
        return var;
    }

    // static
    template <Scope::ScopeLookupMode mode>
    Variable* Scope::Lookup(VariableProxy* proxy, Scope* scope,
        Scope* outer_scope_end, Scope* entry_point,
        bool force_context_allocation)
    {
        if (mode == kDeserializedScope) {
            Variable* var = entry_point->variables_.Lookup(proxy->raw_name());
            if (var != nullptr)
                return var;
        }

        while (true) {
            DCHECK_IMPLIES(mode == kParsedScope, !scope->is_debug_evaluate_scope_);
            // Short-cut: whenever we find a debug-evaluate scope, just look everything
            // up dynamically. Debug-evaluate doesn't properly create scope info for the
            // lookups it does. It may not have a valid 'this' declaration, and anything
            // accessed through debug-evaluate might invalidly resolve to
            // stack-allocated variables.
            // TODO(yangguo): Remove once debug-evaluate creates proper ScopeInfo for
            // the scopes in which it's evaluating.
            if (mode == kDeserializedScope && V8_UNLIKELY(scope->is_debug_evaluate_scope_)) {
                return entry_point->NonLocal(proxy->raw_name(), VariableMode::kDynamic);
            }

            // Try to find the variable in this scope.
            Variable* var = mode == kParsedScope ? scope->LookupLocal(proxy->raw_name())
                                                 : scope->LookupInScopeInfo(
                                                     proxy->raw_name(), entry_point);

            // We found a variable and we are done. (Even if there is an 'eval' in this
            // scope which introduces the same variable again, the resulting variable
            // remains the same.)
            if (var != nullptr) {
                if (mode == kParsedScope && force_context_allocation && !var->is_dynamic()) {
                    var->ForceContextAllocation();
                }
                return var;
            }

            if (scope->outer_scope_ == outer_scope_end)
                break;

            DCHECK(!scope->is_script_scope());
            if (V8_UNLIKELY(scope->is_with_scope())) {
                return LookupWith(proxy, scope, outer_scope_end, entry_point,
                    force_context_allocation);
            }
            if (V8_UNLIKELY(scope->is_declaration_scope() && scope->AsDeclarationScope()->calls_sloppy_eval())) {
                return LookupSloppyEval(proxy, scope, outer_scope_end, entry_point,
                    force_context_allocation);
            }

            force_context_allocation |= scope->is_function_scope();
            scope = scope->outer_scope_;
            // TODO(verwaest): Separate through AnalyzePartially.
            if (mode == kParsedScope && !scope->scope_info_.is_null()) {
                return Lookup<kDeserializedScope>(proxy, scope, outer_scope_end, scope);
            }
        }

        // We may just be trying to find all free variables. In that case, don't
        // declare them in the outer scope.
        // TODO(marja): Separate Lookup for preparsed scopes better.
        if (mode == kParsedScope && !scope->is_script_scope()) {
            return nullptr;
        }

        // No binding has been found. Declare a variable on the global object.
        return scope->AsDeclarationScope()->DeclareDynamicGlobal(
            proxy->raw_name(), NORMAL_VARIABLE,
            mode == kDeserializedScope ? entry_point : scope);
    }

    template Variable* Scope::Lookup<Scope::kParsedScope>(
        VariableProxy* proxy, Scope* scope, Scope* outer_scope_end,
        Scope* entry_point, bool force_context_allocation);
    template Variable* Scope::Lookup<Scope::kDeserializedScope>(
        VariableProxy* proxy, Scope* scope, Scope* outer_scope_end,
        Scope* entry_point, bool force_context_allocation);

    Variable* Scope::LookupWith(VariableProxy* proxy, Scope* scope,
        Scope* outer_scope_end, Scope* entry_point,
        bool force_context_allocation)
    {
        DCHECK(scope->is_with_scope());

        Variable* var = scope->outer_scope_->scope_info_.is_null()
            ? Lookup<kParsedScope>(proxy, scope->outer_scope_, outer_scope_end,
                nullptr, force_context_allocation)
            : Lookup<kDeserializedScope>(proxy, scope->outer_scope_,
                outer_scope_end, entry_point);

        if (var == nullptr)
            return var;

        // The current scope is a with scope, so the variable binding can not be
        // statically resolved. However, note that it was necessary to do a lookup
        // in the outer scope anyway, because if a binding exists in an outer
        // scope, the associated variable has to be marked as potentially being
        // accessed from inside of an inner with scope (the property may not be in
        // the 'with' object).
        if (!var->is_dynamic() && var->IsUnallocated()) {
            DCHECK(!scope->already_resolved_);
            var->set_is_used();
            var->ForceContextAllocation();
            if (proxy->is_assigned())
                var->set_maybe_assigned();
        }
        if (entry_point != nullptr)
            entry_point->variables_.Remove(var);
        Scope* target = entry_point == nullptr ? scope : entry_point;
        return target->NonLocal(proxy->raw_name(), VariableMode::kDynamic);
    }

    Variable* Scope::LookupSloppyEval(VariableProxy* proxy, Scope* scope,
        Scope* outer_scope_end, Scope* entry_point,
        bool force_context_allocation)
    {
        DCHECK(scope->is_declaration_scope() && scope->AsDeclarationScope()->calls_sloppy_eval());

        // If we're compiling eval, it's possible that the outer scope is the first
        // ScopeInfo-backed scope.
        Scope* entry = entry_point == nullptr ? scope->outer_scope_ : entry_point;
        Variable* var = scope->outer_scope_->scope_info_.is_null()
            ? Lookup<kParsedScope>(proxy, scope->outer_scope_, outer_scope_end,
                nullptr, force_context_allocation)
            : Lookup<kDeserializedScope>(proxy, scope->outer_scope_,
                outer_scope_end, entry);
        if (var == nullptr)
            return var;

        // A variable binding may have been found in an outer scope, but the current
        // scope makes a sloppy 'eval' call, so the found variable may not be the
        // correct one (the 'eval' may introduce a binding with the same name). In
        // that case, change the lookup result to reflect this situation. Only
        // scopes that can host var bindings (declaration scopes) need be considered
        // here (this excludes block and catch scopes), and variable lookups at
        // script scope are always dynamic.
        if (var->IsGlobalObjectProperty()) {
            Scope* target = entry_point == nullptr ? scope : entry_point;
            return target->NonLocal(proxy->raw_name(), VariableMode::kDynamicGlobal);
        }

        if (var->is_dynamic())
            return var;

        Variable* invalidated = var;
        if (entry_point != nullptr)
            entry_point->variables_.Remove(invalidated);

        Scope* target = entry_point == nullptr ? scope : entry_point;
        var = target->NonLocal(proxy->raw_name(), VariableMode::kDynamicLocal);
        var->set_local_if_not_shadowed(invalidated);

        return var;
    }

    void Scope::ResolveVariable(ParseInfo* info, VariableProxy* proxy)
    {
        DCHECK(info->script_scope()->is_script_scope());
        DCHECK(!proxy->is_resolved());
        Variable* var = Lookup<kParsedScope>(proxy, this, nullptr);
        DCHECK_NOT_NULL(var);
        ResolveTo(info, proxy, var);
    }

    namespace {

        void SetNeedsHoleCheck(Variable* var, VariableProxy* proxy)
        {
            proxy->set_needs_hole_check();
            var->ForceHoleInitialization();
        }

        void UpdateNeedsHoleCheck(Variable* var, VariableProxy* proxy, Scope* scope)
        {
            if (var->mode() == VariableMode::kDynamicLocal) {
                // Dynamically introduced variables never need a hole check (since they're
                // VariableMode::kVar bindings, either from var or function declarations),
                // but the variable they shadow might need a hole check, which we want to do
                // if we decide that no shadowing variable was dynamically introoduced.
                DCHECK_EQ(kCreatedInitialized, var->initialization_flag());
                return UpdateNeedsHoleCheck(var->local_if_not_shadowed(), proxy, scope);
            }

            if (var->initialization_flag() == kCreatedInitialized)
                return;

            // It's impossible to eliminate module import hole checks here, because it's
            // unknown at compilation time whether the binding referred to in the
            // exporting module itself requires hole checks.
            if (var->location() == VariableLocation::MODULE && !var->IsExport()) {
                return SetNeedsHoleCheck(var, proxy);
            }

            // Check if the binding really needs an initialization check. The check
            // can be skipped in the following situation: we have a VariableMode::kLet or
            // VariableMode::kConst binding, both the Variable and the VariableProxy have
            // the same declaration scope (i.e. they are both in global code, in the same
            // function or in the same eval code), the VariableProxy is in the source
            // physically located after the initializer of the variable, and that the
            // initializer cannot be skipped due to a nonlinear scope.
            //
            // The condition on the closure scopes is a conservative check for
            // nested functions that access a binding and are called before the
            // binding is initialized:
            //   function() { f(); let x = 1; function f() { x = 2; } }
            //
            // The check cannot be skipped on non-linear scopes, namely switch
            // scopes, to ensure tests are done in cases like the following:
            //   switch (1) { case 0: let x = 2; case 1: f(x); }
            // The scope of the variable needs to be checked, in case the use is
            // in a sub-block which may be linear.
            if (var->scope()->GetClosureScope() != scope->GetClosureScope()) {
                return SetNeedsHoleCheck(var, proxy);
            }

            // We should always have valid source positions.
            DCHECK_NE(var->initializer_position(), kNoSourcePosition);
            DCHECK_NE(proxy->position(), kNoSourcePosition);

            if (var->scope()->is_nonlinear() || var->initializer_position() >= proxy->position()) {
                return SetNeedsHoleCheck(var, proxy);
            }
        }

    } // anonymous namespace

    void Scope::ResolveTo(ParseInfo* info, VariableProxy* proxy, Variable* var)
    {
        DCHECK_NOT_NULL(var);
        UpdateNeedsHoleCheck(var, proxy, this);
        proxy->BindTo(var);
    }

    void Scope::ResolvePreparsedVariable(VariableProxy* proxy, Scope* scope,
        Scope* end)
    {
        // Resolve the variable in all parsed scopes to force context allocation.
        for (; scope != end; scope = scope->outer_scope_) {
            Variable* var = scope->LookupLocal(proxy->raw_name());
            if (var != nullptr) {
                var->set_is_used();
                if (!var->is_dynamic()) {
                    var->ForceContextAllocation();
                    if (proxy->is_assigned())
                        var->set_maybe_assigned();
                    return;
                }
            }
        }
    }

    bool Scope::ResolveVariablesRecursively(ParseInfo* info)
    {
        DCHECK(info->script_scope()->is_script_scope());
        // Lazy parsed declaration scopes are already partially analyzed. If there are
        // unresolved references remaining, they just need to be resolved in outer
        // scopes.
        if (WasLazilyParsed(this)) {
            DCHECK_EQ(variables_.occupancy(), 0);
            Scope* end = info->scope();
            // Resolve in all parsed scopes except for the script scope.
            if (!end->is_script_scope())
                end = end->outer_scope();

            for (VariableProxy* proxy : unresolved_list_) {
                ResolvePreparsedVariable(proxy, outer_scope(), end);
            }
        } else {
            // Resolve unresolved variables for this scope.
            for (VariableProxy* proxy : unresolved_list_) {
                ResolveVariable(info, proxy);
            }

            // Resolve unresolved variables for inner scopes.
            for (Scope* scope = inner_scope_; scope != nullptr;
                 scope = scope->sibling_) {
                if (!scope->ResolveVariablesRecursively(info))
                    return false;
            }
        }
        return true;
    }

    bool Scope::MustAllocate(Variable* var)
    {
        DCHECK(var->location() != VariableLocation::MODULE);
        // Give var a read/write use if there is a chance it might be accessed
        // via an eval() call.  This is only possible if the variable has a
        // visible name.
        if (!var->raw_name()->IsEmpty() && (inner_scope_calls_eval_ || is_catch_scope() || is_script_scope())) {
            var->set_is_used();
            if (inner_scope_calls_eval_)
                var->set_maybe_assigned();
        }
        DCHECK(!var->has_forced_context_allocation() || var->is_used());
        // Global variables do not need to be allocated.
        return !var->IsGlobalObjectProperty() && var->is_used();
    }

    bool Scope::MustAllocateInContext(Variable* var)
    {
        // If var is accessed from an inner scope, or if there is a possibility
        // that it might be accessed from the current or an inner scope (through
        // an eval() call or a runtime with lookup), it must be allocated in the
        // context.
        //
        // Temporary variables are always stack-allocated.  Catch-bound variables are
        // always context-allocated.
        if (var->mode() == VariableMode::kTemporary)
            return false;
        if (is_catch_scope())
            return true;
        if ((is_script_scope() || is_eval_scope()) && IsLexicalVariableMode(var->mode())) {
            return true;
        }
        return var->has_forced_context_allocation() || inner_scope_calls_eval_;
    }

    void Scope::AllocateStackSlot(Variable* var)
    {
        if (is_block_scope()) {
            outer_scope()->GetDeclarationScope()->AllocateStackSlot(var);
        } else {
            var->AllocateTo(VariableLocation::LOCAL, num_stack_slots_++);
        }
    }

    void Scope::AllocateHeapSlot(Variable* var)
    {
        var->AllocateTo(VariableLocation::CONTEXT, num_heap_slots_++);
    }

    void DeclarationScope::AllocateParameterLocals()
    {
        DCHECK(is_function_scope());

        bool has_mapped_arguments = false;
        if (arguments_ != nullptr) {
            DCHECK(!is_arrow_scope());
            if (MustAllocate(arguments_) && !has_arguments_parameter_) {
                // 'arguments' is used and does not refer to a function
                // parameter of the same name. If the arguments object
                // aliases formal parameters, we conservatively allocate
                // them specially in the loop below.
                has_mapped_arguments = GetArgumentsType() == CreateArgumentsType::kMappedArguments;
            } else {
                // 'arguments' is unused. Tell the code generator that it does not need to
                // allocate the arguments object by nulling out arguments_.
                arguments_ = nullptr;
            }
        }

        // The same parameter may occur multiple times in the parameters_ list.
        // If it does, and if it is not copied into the context object, it must
        // receive the highest parameter index for that parameter; thus iteration
        // order is relevant!
        for (int i = num_parameters() - 1; i >= 0; --i) {
            Variable* var = params_[i];
            DCHECK_NOT_NULL(var);
            DCHECK(!has_rest_ || var != rest_parameter());
            DCHECK_EQ(this, var->scope());
            if (has_mapped_arguments) {
                var->set_is_used();
                var->set_maybe_assigned();
                var->ForceContextAllocation();
            }
            AllocateParameter(var, i);
        }
    }

    void DeclarationScope::AllocateParameter(Variable* var, int index)
    {
        if (!MustAllocate(var))
            return;
        if (has_forced_context_allocation_for_parameters() || MustAllocateInContext(var)) {
            DCHECK(var->IsUnallocated() || var->IsContextSlot());
            if (var->IsUnallocated())
                AllocateHeapSlot(var);
        } else {
            DCHECK(var->IsUnallocated() || var->IsParameter());
            if (var->IsUnallocated()) {
                var->AllocateTo(VariableLocation::PARAMETER, index);
            }
        }
    }

    void DeclarationScope::AllocateReceiver()
    {
        if (!has_this_declaration())
            return;
        DCHECK_NOT_NULL(receiver());
        DCHECK_EQ(receiver()->scope(), this);
        AllocateParameter(receiver(), -1);
    }

    void Scope::AllocateNonParameterLocal(Variable* var)
    {
        DCHECK_EQ(var->scope(), this);
        if (var->IsUnallocated() && MustAllocate(var)) {
            if (MustAllocateInContext(var)) {
                AllocateHeapSlot(var);
                DCHECK_IMPLIES(is_catch_scope(),
                    var->index() == Context::THROWN_OBJECT_INDEX);
            } else {
                AllocateStackSlot(var);
            }
        }
    }

    void Scope::AllocateNonParameterLocalsAndDeclaredGlobals()
    {
        for (Variable* local : locals_) {
            AllocateNonParameterLocal(local);
        }

        if (is_declaration_scope()) {
            AsDeclarationScope()->AllocateLocals();
        }
    }

    void DeclarationScope::AllocateLocals()
    {
        // For now, function_ must be allocated at the very end.  If it gets
        // allocated in the context, it must be the last slot in the context,
        // because of the current ScopeInfo implementation (see
        // ScopeInfo::ScopeInfo(FunctionScope* scope) constructor).
        if (function_ != nullptr && MustAllocate(function_)) {
            AllocateNonParameterLocal(function_);
        } else {
            function_ = nullptr;
        }

        DCHECK(!has_rest_ || !MustAllocate(rest_parameter()) || !rest_parameter()->IsUnallocated());

        if (new_target_ != nullptr && !MustAllocate(new_target_)) {
            new_target_ = nullptr;
        }

        NullifyRareVariableIf(RareVariable::kThisFunction,
            [=](Variable* var) { return !MustAllocate(var); });
    }

    void ModuleScope::AllocateModuleVariables()
    {
        for (const auto& it : module()->regular_imports()) {
            Variable* var = LookupLocal(it.first);
            var->AllocateTo(VariableLocation::MODULE, it.second->cell_index);
            DCHECK(!var->IsExport());
        }

        for (const auto& it : module()->regular_exports()) {
            Variable* var = LookupLocal(it.first);
            var->AllocateTo(VariableLocation::MODULE, it.second->cell_index);
            DCHECK(var->IsExport());
        }
    }

    void Scope::AllocateVariablesRecursively()
    {
        this->ForEach([](Scope* scope) -> Iteration {
            DCHECK(!scope->already_resolved_);
            if (WasLazilyParsed(scope))
                return Iteration::kContinue;
            DCHECK_EQ(Context::MIN_CONTEXT_SLOTS, scope->num_heap_slots_);

            // Allocate variables for this scope.
            // Parameters must be allocated first, if any.
            if (scope->is_declaration_scope()) {
                if (scope->is_function_scope()) {
                    scope->AsDeclarationScope()->AllocateParameterLocals();
                }
                scope->AsDeclarationScope()->AllocateReceiver();
            }
            scope->AllocateNonParameterLocalsAndDeclaredGlobals();

            // Force allocation of a context for this scope if necessary. For a 'with'
            // scope and for a function scope that makes an 'eval' call we need a
            // context, even if no local variables were statically allocated in the
            // scope. Likewise for modules and function scopes representing asm.js
            // modules. Also force a context, if the scope is stricter than the outer
            // scope.
            bool must_have_context = scope->is_with_scope() || scope->is_module_scope() || scope->IsAsmModule() || scope->ForceContextForLanguageMode() || (scope->is_function_scope() && scope->AsDeclarationScope()->calls_sloppy_eval()) || (scope->is_block_scope() && scope->is_declaration_scope() && scope->AsDeclarationScope()->calls_sloppy_eval());

            // If we didn't allocate any locals in the local context, then we only
            // need the minimal number of slots if we must have a context.
            if (scope->num_heap_slots_ == Context::MIN_CONTEXT_SLOTS && !must_have_context) {
                scope->num_heap_slots_ = 0;
            }

            // Allocation done.
            DCHECK(scope->num_heap_slots_ == 0 || scope->num_heap_slots_ >= Context::MIN_CONTEXT_SLOTS);
            return Iteration::kDescend;
        });
    }

    void Scope::AllocateScopeInfosRecursively(Isolate* isolate,
        MaybeHandle<ScopeInfo> outer_scope)
    {
        DCHECK(scope_info_.is_null());
        MaybeHandle<ScopeInfo> next_outer_scope = outer_scope;

        if (NeedsScopeInfo()) {
            scope_info_ = ScopeInfo::Create(isolate, zone(), this, outer_scope);
            // The ScopeInfo chain should mirror the context chain, so we only link to
            // the next outer scope that needs a context.
            if (NeedsContext())
                next_outer_scope = scope_info_;
        }

        // Allocate ScopeInfos for inner scopes.
        for (Scope* scope = inner_scope_; scope != nullptr; scope = scope->sibling_) {
            if (!scope->is_function_scope() || scope->AsDeclarationScope()->ShouldEagerCompile()) {
                scope->AllocateScopeInfosRecursively(isolate, next_outer_scope);
            }
        }
    }

    // static
    void DeclarationScope::AllocateScopeInfos(ParseInfo* info, Isolate* isolate)
    {
        DeclarationScope* scope = info->literal()->scope();
        if (!scope->scope_info_.is_null())
            return; // Allocated by outer function.

        MaybeHandle<ScopeInfo> outer_scope;
        if (scope->outer_scope_ != nullptr) {
            outer_scope = scope->outer_scope_->scope_info_;
        }

        scope->AllocateScopeInfosRecursively(isolate, outer_scope);

        // The debugger expects all shared function infos to contain a scope info.
        // Since the top-most scope will end up in a shared function info, make sure
        // it has one, even if it doesn't need a scope info.
        // TODO(jochen|yangguo): Remove this requirement.
        if (scope->scope_info_.is_null()) {
            scope->scope_info_ = ScopeInfo::Create(isolate, scope->zone(), scope, outer_scope);
        }

        // Ensuring that the outer script scope has a scope info avoids having
        // special case for native contexts vs other contexts.
        if (info->script_scope() && info->script_scope()->scope_info_.is_null()) {
            info->script_scope()->scope_info_ = handle(ScopeInfo::Empty(isolate), isolate);
        }
    }

    int Scope::ContextLocalCount() const
    {
        if (num_heap_slots() == 0)
            return 0;
        Variable* function = is_function_scope() ? AsDeclarationScope()->function_var() : nullptr;
        bool is_function_var_in_context = function != nullptr && function->IsContextSlot();
        return num_heap_slots() - Context::MIN_CONTEXT_SLOTS - (is_function_var_in_context ? 1 : 0);
    }

    Variable* ClassScope::DeclarePrivateName(const AstRawString* name,
        bool* was_added)
    {
        Variable* result = EnsureRareData()->private_name_map.Declare(
            zone(), this, name, VariableMode::kConst, NORMAL_VARIABLE,
            InitializationFlag::kNeedsInitialization,
            MaybeAssignedFlag::kMaybeAssigned, was_added);
        if (*was_added) {
            locals_.Add(result);
        }
        result->ForceContextAllocation();
        return result;
    }

    Variable* ClassScope::LookupLocalPrivateName(const AstRawString* name)
    {
        if (rare_data_ == nullptr) {
            return nullptr;
        }
        return rare_data_->private_name_map.Lookup(name);
    }

    UnresolvedList::Iterator ClassScope::GetUnresolvedPrivateNameTail()
    {
        if (rare_data_ == nullptr) {
            return UnresolvedList::Iterator();
        }
        return rare_data_->unresolved_private_names.end();
    }

    void ClassScope::ResetUnresolvedPrivateNameTail(UnresolvedList::Iterator tail)
    {
        if (rare_data_ == nullptr || rare_data_->unresolved_private_names.end() == tail) {
            return;
        }

        bool tail_is_empty = tail == UnresolvedList::Iterator();
        if (tail_is_empty) {
            // If the saved tail is empty, the list used to be empty, so clear it.
            rare_data_->unresolved_private_names.Clear();
        } else {
            rare_data_->unresolved_private_names.Rewind(tail);
        }
    }

    void ClassScope::MigrateUnresolvedPrivateNameTail(
        AstNodeFactory* ast_node_factory, UnresolvedList::Iterator tail)
    {
        if (rare_data_ == nullptr || rare_data_->unresolved_private_names.end() == tail) {
            return;
        }
        UnresolvedList migrated_names;

        // If the saved tail is empty, the list used to be empty, so we should
        // migrate everything after the head.
        bool tail_is_empty = tail == UnresolvedList::Iterator();
        UnresolvedList::Iterator it = tail_is_empty ? rare_data_->unresolved_private_names.begin() : tail;

        for (; it != rare_data_->unresolved_private_names.end(); ++it) {
            VariableProxy* proxy = *it;
            VariableProxy* copy = ast_node_factory->CopyVariableProxy(proxy);
            migrated_names.Add(copy);
        }

        // Replace with the migrated copies.
        if (tail_is_empty) {
            rare_data_->unresolved_private_names.Clear();
        } else {
            rare_data_->unresolved_private_names.Rewind(tail);
        }
        rare_data_->unresolved_private_names.Append(std::move(migrated_names));
    }

    void ClassScope::AddUnresolvedPrivateName(VariableProxy* proxy)
    {
        // During a reparse, already_resolved_ may be true here, because
        // the class scope is deserialized while the function scope inside may
        // be new.
        DCHECK(!proxy->is_resolved());
        DCHECK(proxy->IsPrivateName());
        EnsureRareData()->unresolved_private_names.Add(proxy);
    }

    Variable* ClassScope::LookupPrivateNameInScopeInfo(const AstRawString* name)
    {
        DCHECK(!scope_info_.is_null());
        DCHECK_NULL(LookupLocalPrivateName(name));
        DisallowHeapAllocation no_gc;

        String name_handle = *name->string();
        VariableMode mode;
        InitializationFlag init_flag;
        MaybeAssignedFlag maybe_assigned_flag;
        int index = ScopeInfo::ContextSlotIndex(*scope_info_, name_handle, &mode,
            &init_flag, &maybe_assigned_flag);
        if (index < 0) {
            return nullptr;
        }

        DCHECK_EQ(mode, VariableMode::kConst);
        DCHECK_EQ(init_flag, InitializationFlag::kNeedsInitialization);
        DCHECK_EQ(maybe_assigned_flag, MaybeAssignedFlag::kMaybeAssigned);

        // Add the found private name to the map to speed up subsequent
        // lookups for the same name.
        bool was_added;
        Variable* var = DeclarePrivateName(name, &was_added);
        DCHECK(was_added);
        var->AllocateTo(VariableLocation::CONTEXT, index);
        return var;
    }

    Variable* ClassScope::LookupPrivateName(VariableProxy* proxy)
    {
        DCHECK(!proxy->is_resolved());

        for (Scope* scope = this; !scope->is_script_scope();
             scope = scope->outer_scope_) {
            if (!scope->is_class_scope())
                continue; // Only search in class scopes
            ClassScope* class_scope = scope->AsClassScope();
            // Try finding it in the private name map first, if it can't be found,
            // try the deseralized scope info.
            Variable* var = class_scope->LookupLocalPrivateName(proxy->raw_name());
            if (var == nullptr && !class_scope->scope_info_.is_null()) {
                var = class_scope->LookupPrivateNameInScopeInfo(proxy->raw_name());
            }
            return var;
        }
        return nullptr;
    }

    bool ClassScope::ResolvePrivateNames(ParseInfo* info)
    {
        if (rare_data_ == nullptr || rare_data_->unresolved_private_names.is_empty()) {
            return true;
        }

        UnresolvedList& list = rare_data_->unresolved_private_names;
        for (VariableProxy* proxy : list) {
            Variable* var = LookupPrivateName(proxy);
            if (var == nullptr) {
                Scanner::Location loc = proxy->location();
                info->pending_error_handler()->ReportMessageAt(
                    loc.beg_pos, loc.end_pos,
                    MessageTemplate::kInvalidPrivateFieldResolution, proxy->raw_name(),
                    kSyntaxError);
                return false;
            } else {
                var->set_is_used();
                proxy->BindTo(var);
            }
        }

        // By now all unresolved private names should be resolved so
        // clear the list.
        list.Clear();
        return true;
    }

    VariableProxy* ClassScope::ResolvePrivateNamesPartially()
    {
        if (rare_data_ == nullptr || rare_data_->unresolved_private_names.is_empty()) {
            return nullptr;
        }

        ClassScope* outer_class_scope = outer_scope_ == nullptr ? nullptr : outer_scope_->GetClassScope();
        UnresolvedList& unresolved = rare_data_->unresolved_private_names;
        bool has_private_names = rare_data_->private_name_map.capacity() > 0;

        // If the class itself does not have private names, nor does it have
        // an outer class scope, then we are certain any private name access
        // inside cannot be resolved.
        if (!has_private_names && outer_class_scope == nullptr && !unresolved.is_empty()) {
            return unresolved.first();
        }

        for (VariableProxy* proxy = unresolved.first(); proxy != nullptr;) {
            DCHECK(proxy->IsPrivateName());
            VariableProxy* next = proxy->next_unresolved();
            unresolved.Remove(proxy);
            Variable* var = nullptr;

            // If we can find private name in the current class scope, we can bind
            // them immediately because it's going to shadow any outer private names.
            if (has_private_names) {
                var = LookupLocalPrivateName(proxy->raw_name());
                if (var != nullptr) {
                    var->set_is_used();
                    proxy->BindTo(var);
                }
            }

            // If the current scope does not have declared private names,
            // try looking from the outer class scope later.
            if (var == nullptr) {
                // There's no outer class scope so we are certain that the variable
                // cannot be resolved later.
                if (outer_class_scope == nullptr) {
                    return proxy;
                }

                // The private name may be found later in the outer class scope,
                // so push it to the outer sopce.
                outer_class_scope->AddUnresolvedPrivateName(proxy);
            }

            proxy = next;
        }

        DCHECK(unresolved.is_empty());
        return nullptr;
    }

} // namespace internal
} // namespace v8
